package com.weds.data_sync.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.weds.data_sync.advice.exception.BizCodeException;
import com.weds.data_sync.constant.BizCodeEnum;
import com.weds.data_sync.constant.ScriptServiceConstant;
import com.weds.data_sync.mapper.DataSyncDbInfoMapper;
import com.weds.data_sync.mapper.DataSyncPlanRecordMapper;
import com.weds.data_sync.mapper.DataSyncScriptMapper;
import com.weds.data_sync.mapper.DataSyncScriptPlanMapper;
import com.weds.data_sync.pojo.dto.PageDto;
import com.weds.data_sync.pojo.dto.ScriptPlanDto;
import com.weds.data_sync.pojo.dto.ScriptRequestDto;
import com.weds.data_sync.pojo.entity.DataSyncDbInfoDO;
import com.weds.data_sync.pojo.entity.DataSyncScriptDO;
import com.weds.data_sync.pojo.entity.DataSyncScriptPlanDO;
import com.weds.data_sync.service.ExecutePlan;
import com.weds.data_sync.service.ScriptPlanService;
import com.weds.data_sync.service.ScriptService;
import com.weds.data_sync.util.CommonUtil;
import com.weds.data_sync.util.JsonData;
import com.weds.data_sync.util.xxl_component.XxlProperties;
import com.weds.data_sync.util.xxl_component.XxlTemplate;
import com.weds.data_sync.util.xxl_component.pojo.TaskDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.*;

/**
 * <p>
 * 执行方案 服务实现类
 * </p>
 *
 * @author lhw
 * @since 2023-12-29
 */
@Slf4j
@Service
public class ScriptPlanServiceImp extends ServiceImpl<DataSyncScriptPlanMapper, DataSyncScriptPlanDO> implements ScriptPlanService {

    @Autowired
    private DataSyncScriptPlanMapper planMapper;

    @Autowired
    private DataSyncScriptMapper scriptMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    private DataSyncDbInfoMapper dbInfoMapper;

    @Autowired
    private ScriptService scriptService;

    @Autowired
    private WebClient webClient;

    @Autowired
    private DataSyncPlanRecordMapper planRecordMapper;

    @Autowired
    private XxlTemplate xxlTemplate;

    @Autowired
    private XxlProperties xxlProperties;


    @Override
    public Integer add(ScriptPlanDto scriptPlanDto) {

        DataSyncScriptPlanDO planDO = new DataSyncScriptPlanDO();
        BeanUtils.copyProperties(scriptPlanDto, planDO);
        // 生成唯一执行计划编号
        String planNo = IdUtil.fastSimpleUUID();
        planDO.setPlanNo(planNo);

        createTaskAndRun(planDO);

        return planMapper.insert(planDO);

    }

    /**
     * 动态创建任务并执行
     */
    public void createTaskAndRun(DataSyncScriptPlanDO planDO) {
        if (planDO.getRunType().equals(ScriptServiceConstant.RunType.JOB)) {
            TaskDto taskDto = createTaskDto(planDO.getPlanName());
            // 执行器id
            planDO.setExecutorId(taskDto.getJobGroup() + "");
            if (Objects.isNull(taskDto)) {
                throw new RuntimeException("创建任务对象失败");
            }
            taskDto.setScheduleConf(planDO.getRunCron());
            taskDto.setExecutorParam(planDO.getPlanNo());
            // 动态创建调度
            Object result = xxlTemplate.addTaskItem(taskDto);
            LinkedHashMap<String, Object> jsonData = (LinkedHashMap<String, Object>) result;
            if (Integer.parseInt(jsonData.get("code").toString()) == 0) {
                log.info("创建执行计划调度任务成功, {}", jsonData);
                // 任务调度id
                planDO.setJobTaskId(jsonData.get("data").toString());
                if (!Objects.isNull(planDO.getIsOnce()) && planDO.getIsOnce() == 1) {
                    Object startResult = xxlTemplate.startTaskItem(Integer.parseInt(jsonData.get("data").toString()));
                    LinkedHashMap<String, Object> startResultMap = (LinkedHashMap<String, Object>) startResult;
                    if (Integer.parseInt(startResultMap.get("code").toString()) == 0) {
                        log.info("任务启动成功: {}", planDO);
                    } else {
                        throw new RuntimeException("任务启动失败: " + jsonData.get("msg").toString());
                    }
                }
            } else {
                throw new RuntimeException("创建任务失败: " + jsonData.get("msg").toString());
            }
        }
    }

    @Nullable
    public TaskDto createTaskDto(String desc) {
        Object executorListObj = xxlTemplate.queryExecutorList(0, 10, xxlProperties.getAppname(), "");
        LinkedHashMap<String, Object> jsonData = (LinkedHashMap<String, Object>) executorListObj;
        if (Integer.parseInt(jsonData.get("code").toString()) == 0) {

            LinkedHashMap<String, Object> data = (LinkedHashMap<String, Object>) jsonData.get("data");
            List<Map<String, Object>> list = (List<Map<String, Object>>) data.get("data");
            if (data.isEmpty()) {
                log.error("未找到执行器，请提前创建执行器。");
                return null;
            } else {
                Map<String, Object> appInfo = list.get(0);
                int executorId = Integer.parseInt(appInfo.get("id").toString());
                TaskDto taskDto = TaskDto.builder()
                        .jobGroup(executorId)
                        .jobDesc("数据同步_执行计划任务:" + desc)
                        .author("lihw")
                        .scheduleType("CRON")
                        .scheduleConf("* * * * * ?")
                        .executorHandler("planJobHandler")
                        .glueType("BEAN")
                        .build();
                return taskDto;
            }
        } else {
            log.error(jsonData.get("msg").toString());
            return null;
        }
    }

    @Override
    public Integer del(List<Long> planIds) {
        if (Objects.isNull(planIds) || planIds.isEmpty()) {
            throw new BizCodeException(BizCodeEnum.PLAN_DEL_NOCHOOSE);
        }
        // 删除远程调度任务
        List<DataSyncScriptPlanDO> planDOList = planMapper.selectBatchIds(planIds);
        for (int i = 0; i < planDOList.size(); i++) {
            DataSyncScriptPlanDO planDO = planDOList.get(i);
            JsonData jsonData = stopPlan(planDO.getPlanNo());
            if (jsonData.getCode() == 0) {
                log.info("停止调度成功: {}", planDO);
            } else {
                log.info("停止调度失败: {}", jsonData.getMsg());
                throw new RuntimeException("停止调度失败: " + jsonData.getMsg());
            }
        }

        return planMapper.deleteBatchIds(planIds);
    }

    @Override
    public Integer update(ScriptPlanDto scriptPlanDto) {
        if (Objects.isNull(scriptPlanDto.getId())) {
            throw new BizCodeException(BizCodeEnum.PLAN_UPDATE_UNKNOW);
        }
        DataSyncScriptPlanDO planDO = new DataSyncScriptPlanDO();
        BeanUtils.copyProperties(scriptPlanDto, planDO);
        return planMapper.update(planDO, new QueryWrapper<DataSyncScriptPlanDO>()
                .eq("id", planDO.getId()));
    }

    @Override
    public JsonData.PageData<DataSyncScriptPlanDO> queryData(PageDto pageDto) {
        Page<DataSyncScriptPlanDO> pageInfo = new Page<>(pageDto.getPage(), pageDto.getSize());
        planMapper.selectPage(pageInfo, new QueryWrapper<DataSyncScriptPlanDO>()
                .eq("use_status_id", 1)
                .eq("is_delete", 0)
                .orderByAsc("id"));
        return CommonUtil.pageToDto(pageInfo);
    }

    @Override
    public JsonData execute(String planNo, Boolean asyncFlag) {

        DataSyncScriptPlanDO planDO = planMapper.selectOne(new QueryWrapper<DataSyncScriptPlanDO>()
                .eq("plan_no", planNo)
                .eq("use_status_id", 1)
                .eq("is_delete", 0));

        List<DataSyncScriptDO> scriptList = scriptMapper.selectBatchIds(planDO.getScriptArr());
        DataSyncDbInfoDO dbInfoDO = dbInfoMapper.selectOne(new QueryWrapper<DataSyncDbInfoDO>()
                .eq("use_status_id", 1)
                .eq("is_delete", 0)
                .eq("ds_type", ScriptServiceConstant.DsType.THIRD)
                .last("limit 1"));

        ScriptRequestDto.DBInfo dbInfo = ScriptRequestDto.DBInfo.builder()
                .ip(dbInfoDO.getIp())
                .port(dbInfoDO.getPort())
                .dbname(dbInfoDO.getDbName())
                .username(dbInfoDO.getUsername())
                .password(dbInfoDO.getPassword())
                .type(dbInfoDO.getDbType())
                .build();

        ExecutePlan executePlan = new ExecutePlanImpl(
                planDO,
                scriptList,
                dbInfo,
                redisTemplate,
                applicationEventPublisher,
                scriptService,
                planRecordMapper
        );
        if (asyncFlag) {
            executePlan.executeScriptAsync(webClient);
            return JsonData.buildSuccess();
        } else {
            Object result = executePlan.executeScript();
            return JsonData.buildSuccess(result);
        }

    }

    @Override
    public JsonData startPlan(String planNo) {

        DataSyncScriptPlanDO planDO = planMapper.selectOne(new QueryWrapper<DataSyncScriptPlanDO>()
                .eq("plan_no", planNo)
                .eq("use_status_id", 1)
                .eq("is_delete", 0)
                .last("limit 1"));

        Object result = xxlTemplate.startTaskItem(Integer.parseInt(planDO.getJobTaskId()));
        LinkedHashMap<String, Object> resultMap = (LinkedHashMap<String, Object>) result;
        if (Integer.parseInt(resultMap.get("code").toString()) == 0) {
            return JsonData.buildSuccess();
        }

        return JsonData.buildError(resultMap.get("msg").toString());
    }

    @Override
    public JsonData stopPlan(String planNo) {
        DataSyncScriptPlanDO planDO = planMapper.selectOne(new QueryWrapper<DataSyncScriptPlanDO>()
                .eq("plan_no", planNo)
                .eq("use_status_id", 1)
                .eq("is_delete", 0)
                .last("limit 1"));
        Object result = xxlTemplate.stopTaskItem(Integer.parseInt(planDO.getJobTaskId()));
        LinkedHashMap<String, Object> resultMap = (LinkedHashMap<String, Object>) result;
        if (Integer.parseInt(resultMap.get("code").toString()) == 0) {
            return JsonData.buildSuccess();
        }
        return JsonData.buildError(resultMap.get("msg").toString());
    }


    /**
     * 判断某个脚本是否被执行方案应用
     *
     * @param scriptId
     * @return
     */
    @Override
    public Boolean judgeScriptInUse(Long scriptId) {
        List<DataSyncScriptPlanDO> planList = planMapper.selectList(new QueryWrapper<DataSyncScriptPlanDO>()
                .eq("use_status_id", 1)
                .eq("is_delete", 0));
        for (DataSyncScriptPlanDO planItem : planList) {
            List<Integer> scriptArr = planItem.getScriptArr();
            if (scriptArr.contains(scriptId.intValue())) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Boolean judgeScriptInUse(List<Long> scriptIds) {
        List<DataSyncScriptPlanDO> planList = planMapper.selectList(new QueryWrapper<DataSyncScriptPlanDO>()
                .eq("use_status_id", 1)
                .eq("is_delete", 0));
        for (DataSyncScriptPlanDO planItem : planList) {
            List<Integer> scriptArr = planItem.getScriptArr();
            for (Long scriptId : scriptIds) {
                if (scriptArr.contains(scriptId.intValue())) {
                    return true;
                }
            }

        }
        return false;
    }

}
